#-*- test-case-name: fjork.test.test_pool -*-
"""pool.py

Queues and runs requests through the pool 

"""
import os

from twisted.internet import defer
from twisted.internet import error, reactor
from twisted.python import log
from zope import interface

from fjork import ifjork
from fjork import protocols
from fjork import settings


class Pool(object):
    interface.implements(ifjork.IPool)

    def __init__(self):
        self.running = {}
        self.ready = []
        self.queuedRequests = []
        self.maxRequests = 0
        self.processes = {}

    def setProcesses(self, processes):
        log.msg("setting up processes:", processes)
        self.processes = processes      
        self.maxRequests = len(self.processes)
        for address in self.processes.keys():
            self.ready.append(address)

    def queue(self, d, request, path):
        log.msg("queuing:", d, request, path)
        self.queuedRequests.append((d, request, path))
        if len(self.running) < self.maxRequests:
            log.msg("starting processing ...")
            self.process(self.ready.pop())

    def process(self, address):
        d, request, path = self.queuedRequests.pop()
        log.msg("processing:", d, request, path)
        nd = defer.Deferred()
        nd.addCallback(self.processCompleted, address)
        nd.addErrback(self.processFailed)
        nd.chainDeferred(d)
        self.running[address] = (nd, request, path)
        if settings.WEBKIT is True:
            factory = protocols.WebkitServerFactory(nd, request)
        else:
            factory = protocols.FjorkServerFactory(nd, request)
        log.msg("address:", address)
        host = self.processes[address]["host"]
        port = self.processes[address]["port"]
        log.msg("host:", host, "port:", port)
        reactor.connectTCP(host, port, factory)
        log.msg("finished connectTCP!")
        
    def processCompleted(self, response, address):
        log.msg("pool:", "processCompleted:", response)
        del self.running[address]
        self.ready.append(address)
        while self.queuedRequests and self.ready:
            self.process(self.ready.pop())
        return response

    def processFailed(self, failure):
        log.err("pool.processFailed - failure:", failure)
        return failure
    
    def shutdown(self):
        for address, processDescription in self.processes.items():
            process = processDescription["process"]
            try:
                process.signalProcess(15) # SIGTERM should allow process to terminate nicely.
            except error.ProcessExitedAlready, e:
                log.msg("Process exited already: %s"%(e,))

    def _cbProcessReady(self, address):
        log.msg("process %s is ready!"%(address,))
        return address

    def _ebProcessError(self, failure):
        log.err("process failed: %s"%(str(failure),))
        return failure

    def start(self, startServiceDeferred):
        log.msg("starting processes...")
        dl = []
        for processName, processDescription in self.processes.iteritems():
            log.msg("new process:", processName, processDescription)
            d = defer.Deferred()
            d.addCallback(self._cbProcessReady)
            d.addErrback(self._ebProcessError)
            dl.append(d)
            address = "%s:%s"%(processDescription["host"], processDescription["port"])
            p = protocols.FjorkProcessProtocol(address, d)
            args = [processDescription["executable"],
                    ]
            args.extend(processDescription["args"])
            log.msg("process %s starting: %s"%(processName, args))
            process = reactor.spawnProcess(p,
                                           processDescription["executable"],
                                           args,
                                           os.environ,
                                           processDescription.get("path", None) and processDescription["path"] or settings.FJORK_ROOT.path,
                                           )
            self.processes[processName]["process"] = process
        dl = defer.DeferredList(dl)
        dl.addCallback(
            # finally start the service
            self.connect,
            self.processes,
            startServiceDeferred
        )
        return dl

    def connect(self, result, processAddresses, startServiceDeferred):
        startServiceDeferred.callback(result)


        
